/*
 * Copyright (c) 2007, 2008 Johannes Krampf <Johannes.Krampf@htw-dresden.de>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "bstring/bstrlib.h"
#include "database_internal.h"
#include "database.h"
#include "general.h"

struct MemFile* NewMemFile() {
	struct MemFile* newmemfile;
	
	newmemfile = calloc(1, sizeof(struct MemFile));
	
	if (NULL == newmemfile) {
		MemError();
		return NULL;
	}
	
	if (NULL == first) {
		first = newmemfile;
		newmemfile->prev = newmemfile;
		newmemfile->next = newmemfile;
	}else {
		first->prev->next = newmemfile;
		newmemfile->prev = first->prev;
		first->prev = newmemfile;
		newmemfile->next = first;
	}
	
	return newmemfile;
}

int FileExists(char* name) {
	if (NULL == GetFileContent(name)) {
		return 0;
	}
	
	return 1;
}

struct MemFile* FindFile(char* name) {
	struct MemFile* searchptr_fwd = first;
	struct MemFile* searchptr_bwd = first;

	if (NULL == first) {
		return NULL;
	}
	
	do {
		searchptr_bwd = searchptr_bwd->prev;
		if ((strlen(name) == strlen(searchptr_bwd->name)) &&
			(0 == strncmp(searchptr_bwd->name, name, strlen(name))) 
		) {
			return searchptr_bwd;
		}else if (	(strlen(name) == strlen(searchptr_fwd->name)) &&
					(0 == strncmp(searchptr_fwd->name, name, strlen(name)))
				) {
			return searchptr_fwd;
		}
		searchptr_fwd = searchptr_fwd->next;
	}while(searchptr_bwd != searchptr_fwd);
	
	return NULL;
}

int DeleteMemFile(struct MemFile* memfile) {

	if (NULL == memfile) {
		fprintf(stderr, "DeleteMemFile: MemFile is ein Nullpointer.\n");
		return -1;
	}
	if (NULL == memfile->content) {
		fprintf(stderr, "MemFile->content is ein Nullpointer.\n");
		return -1;
	}

	if (memfile->prev == memfile) {
		first = NULL;
	}else {
		if (memfile == first) {
			first = memfile->prev;
		}
		memfile->prev->next = memfile->next;
		memfile->next->prev = memfile->prev;
	}

	bdestroy(memfile->content);
	memfile->content = NULL;
	free(memfile);
	memfile = NULL;
		
	return 0;
}

int DeleteMemFileByName(char* name) {
	return DeleteMemFile(FindFile(name));
}

int ClearDatabase() {
	
	while(NULL != first) {
		DeleteMemFile(first->prev);
	}
	
	return 0;
}

int AddDatabaseEntry(char* name, bstring content) {
	struct MemFile* memfile;

	memfile = FindFile(name);
	
	if (NULL != memfile) {
		printf("AddDatabaseEntry: Eintrag %s bereits vorhanden, ersetze alten Eintrag.\n", name);
		DeleteMemFile(memfile);
		memfile = NULL;
	}
	
	memfile = NewMemFile();
	if (NULL == memfile) {
		MemError();
		return -1;
	}

	strncpy(memfile->name, name, MinLen(strlen(name), 127));
	memfile->name[MinLen(strlen(name), 127)] = '\0';
	memfile->content = content;
	
	return 0;
}

int AddFileToDatabase(char* filename) {
	FILE* input = NULL;
	char *buffer = NULL;
	bstring mfc = cstr2bstr("");
	struct MemFile* memfile = NULL;
	int filesize = 0, bytesread = 0;
	int i;
	
	input = fopen(filename, "rb");
	if (NULL == input) {
		fprintf(stderr, "AddFileToDatabase: Datei %s konnte nicht geoeffnet werden.\n", filename);
		bdestroy(mfc);
		return -1;
	}

	fseek(input , 0 , SEEK_END);
	filesize = ftell(input);
	rewind(input);

	buffer = calloc(filesize, sizeof(char));
	if (NULL == buffer) {
		MemError();
		fclose(input);
		bdestroy(mfc);
		return -1;
	}

	bytesread = fread(buffer, 1, filesize, input);
	if (bytesread != filesize) {
		printf("AddFileToDatabase: Datei %s konnte nicht vollstaendig gelesen werden.\n", filename);
		free(buffer);
		buffer = NULL;
		fclose(input);
		bdestroy(mfc);
		return -1;
	}

	memfile = FindFile(filename);
	
	if (NULL != memfile) {
		fprintf(stderr, "AddFileToDatabase: Eintrag %s bereits vorhanden, ersetze alten Eintrag.\n", filename);
		DeleteMemFile(memfile);
		memfile = NULL;
	}

	memfile = NewMemFile();
	if (NULL == memfile) {
		MemError();
		fclose(input);
		bdestroy(mfc);
		return -1;
	}

	for (i=0;i<filesize;i++) {
		bconchar(mfc, buffer[i]);
	}

	free(buffer);
	buffer = NULL;

	strncpy(memfile->name, filename, MinLen(strlen(filename), 127));
	memfile->name[MinLen(strlen(filename), 127)] = '\0';
	memfile->content = mfc;

	fclose(input);
	return 0;
}

bstring GetFileContent(char* name) {
	bstring buffer = GetFileContentV(name);
	
	if ((NULL == buffer) && (0 == AddFileToDatabase(name))) {
		buffer = GetFileContentV(name);
	}
	
	return buffer;
}

bstring GetFileContentV(char* name) {
	struct MemFile* searchptr_fwd = first;
	struct MemFile* searchptr_bwd = first;
	int found = 0;	

	if (NULL == first) {
		return NULL;
	}
	
	do {
		searchptr_bwd = searchptr_bwd->prev;
		if ((strlen(name) == strlen(searchptr_bwd->name)) &&
			(0 == strncmp(searchptr_bwd->name, name, strlen(name))) 
		) {
			found++;
			break;
		}else if (	(strlen(name) == strlen(searchptr_fwd->name)) &&
					(0 == strncmp(searchptr_fwd->name, name, strlen(name)))
				) {
			searchptr_bwd = searchptr_fwd;
			found++;
			break;
		}
		searchptr_fwd = searchptr_fwd->next;
	}while(searchptr_bwd != searchptr_fwd);
	
	if (0 == found) {
		return NULL;
	}
	
	return searchptr_bwd->content;
}

int LoadDatabaseFromFile(char* filename) {
 	FILE *input = NULL;
	char buffer[128];
	char c;
	char readmode;
	unsigned char *databuffer = NULL;
	struct MemFile *memfile = NULL;
	bstring mfc = NULL;
	unsigned int crc32;
	int bufpos = 0;
	int buftarget = TAR_NAME;
	int length;

 	input = fopen(filename, "r");
 	
 	if (!input) {
 		fprintf(stderr, "LoadDatabaseFromFile: Datei %s konnte nicht geoeffnet werden!\n", filename);
 		return -1;
 	}
/* Dateistruktur:
 * name:length:modeCRC32\ncontent\n */

	while (EOF != (c = getc(input))) {
		if (':' != c) {
			buffer[bufpos++] = c;
		}else {
			buffer[bufpos] = '\0';
			if (TAR_NAME == buftarget) {
				memfile = NewMemFile();
				strncpy(memfile->name, buffer, bufpos);
				buftarget = TAR_LENGTH;
				bufpos = 0;
			}else {
				/* Improve error checking */
				length = atoi(buffer);
				readmode = fgetc(input);
				fread(buffer, 8, 1, input);
				crc32 = HexStringToUInt(buffer);
				if ('\n' != fgetc(input)) {
					fprintf(stderr, "LoadDatabaseFromFile: Fehler in Dateistruktur. Header ist nicht korrekt abgeschlossen.\n");
				}
				
				databuffer = calloc(length+1, sizeof(char));
				if (NULL == databuffer) {
					MemError();
				}
				fread(databuffer, 1, length, input);
				databuffer[length] = '\0';
				if ('\n' != fgetc(input)) {
					fprintf(stderr, "LoadDatabaseFromFile: Fehler in Dateistruktur. Inhalt ist nicht korrekt abgeschlossen.\n");
				}
				if ('h' == readmode) {
					mfc = HexToBin((char *)databuffer);
				}else {
					mfc = cstr2bstr((char *)databuffer);
				}
				if (CRC32(mfc->data, mfc->slen) != crc32) {
					fprintf(stderr, "CRC32-Test fuer %s fehlgeschlagen.\n", memfile->name);
				}
				if (NULL == mfc) {
					DeleteMemFile(memfile);
					MemError();
				}
				memfile->content = mfc;
				free(databuffer);
				databuffer = NULL;
				length = 0;
				buftarget = TAR_NAME;
				bufpos = 0;
			}
		}
	}
	
	fclose(input);

	return 0;
}

int SaveDatabaseToFile(char* filename) {
	FILE* output;
	struct MemFile* current = first;
	bstring buffer = cstr2bstr("");
	bstring message = NULL;
	char readmode = 'a';
	int i;
	unsigned int crc32;
	
	if (NULL == current) {
		fprintf(stderr, "SaveDatabaseToFile: Keine Elemente in der Datenbank gefunden.\n");
		return -1;
	}
	
	output = fopen(filename, "w");
	if (NULL == output) {
		fprintf(stderr, "SaveDatabaseToFile: Datei %s konnte nicht zum Schreiben geoeffnet werden.\n", filename);
		return -1;
	}

	do {
	/* Dateistruktur:
	 * name:length:modeCRC32\ncontent\n */
		readmode = 'a';
		for (i=0;i<current->content->slen;i++) {
			if (((13 != current->content->data[i]) &&
				(10 != current->content->data[i]) &&
				(current->content->data[i] < 32)
			) || (current->content->data[i] > 126)) {
				readmode = 'h';
				break;
			}
		}
		if ('h' == readmode) {
			message = BinToHex(current->content->slen, current->content->data);
		}else {
			message = current->content;	
		}
		
		
		bcatcstr(buffer, current->name);
		bconchar(buffer, ':');
		bconcat(buffer, bformat("%d", message->slen));
		bconchar(buffer, ':');
		bconchar(buffer, readmode);
		crc32 = CRC32(current->content->data, current->content->slen);
		bconcat(buffer, bformat("%08x", crc32));
		bconchar(buffer, '\n');
		bconcat(buffer, message);
		bconchar(buffer, '\n');
		
		fwrite(buffer->data, buffer->slen, 1, output);
		
		buffer = cstr2bstr("");
		current = current->prev;
	} while (first != current);
	fclose(output);
	bdestroy(message);
	
	return 0;
}
